home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 6 / MacMania 6.toast / / Multimedia & Desktop / VideoToolbox / (Demos) / TimeVideo.c < prev    next >
Text File  |  1997-05-30  |  47KB  |  1,038 lines

  1. /*
  2. TimeVideo.c
  3. Copyright © Denis G. Pelli, 1992-1997
  4.  
  5. TimeVideo thoroughly tests the timing and synchronization of each video
  6. screen, as well as the integrity of the clut hardware and software, and saves
  7. the results in a self-explanatory text file, “TimeVideo results”. TimeVideo
  8. runs on all Macs, requiring only System 6.05 or better. A discussion
  9. of movies, lookup table animation, and synchronization appears in the “Video synch” page of
  10. the VideoToolbox web site. The many video driver bugs uncovered by
  11. TimeVideo and its predecessors are reported in the "Video Bugs" page. (For example, version 3 of the
  12. TrueVision NuVista driver assumes zero start in 16- and 32-bit depth modes.) Please
  13. add your results by emailing your “TimeVideo results” to denis@psych.nyu.edu.
  14.  
  15. Despite Apple’s rules that require it, a few video drivers don’t allow you to
  16. read the clut (i.e. use cscGetEntries), or crash if you attempt it. GDGetEntries
  17. (in GDVideo.c) checks against a list of known offenders and returns a statusErr
  18. if the cscGetEntries call is not usable. If we can’t use cscGetEntries then we fall back to a
  19. visual test of the clut, comparing a particular way of loading the clut against
  20. the standard way of loading the clut. Any visible change between these two
  21. conditions suggests an error in the non-standard way of loading the clut.
  22.  
  23.     SaveAndRestoreDevice(device);
  24. Each time you call SaveAndRestoreDevice with a device different from the last it
  25. restores the former device to its last state. A NULL device counts as different,
  26. in that it provokes restoration of the previous device. The initial state of this
  27. routine is as though you had just called it with the NULL device.
  28.  
  29. BUGS:
  30. Version 1.0 hung up on a PowerBook 170 with an Envisio video adapter. I didn’t 
  31. have a chance to figure out why. It works fine on my ordinary PowerBook 170.
  32.  
  33. HISTORY:
  34. 8/23/92    dgp    wrote it, based on my TimeCPU.c
  35. 8/26/92    dgp print the driver version only if it's nonzero.
  36.             added summary at end of printout.
  37. 9/9/92    dgp    changed printout of system vbl rate from %.1f to %.2f
  38.             Added movie rate to measurements and printout.
  39. 9/15/92    dgp    made compatible with System 6.04. Added header to results file.
  40. 9/17/92    dgp    Added QUICKLY switch, to select CopyBits or CopyBitsQuickly.
  41. 10/5/92    dgp    cosmetic changes
  42. 10/6/92    dgp    report program version. Use frames/clut update as the criterion for summary.
  43. 10/9/92    dgp    Fixed summary. Too slow means MORE than one frame per clut update.
  44.             Fixed restoration of clut in direct modes (i.e. 16 and 32 bit modes).
  45.             Renamed to GDRestoreDeviceClut and put it in GDVideo.c.
  46. 10/13/92 dgp Tom Busey reported that frames were going uncounted
  47.             during the clut timing, which seems to be a problem with some video drivers.
  48.             TimeVideo now reports a frame count based solely on timing in secs and
  49.             the separately measured frame rate, which seems to be a reliable.
  50.             It also double checks the timing in secs vs frames, and if
  51.             it finds a discrepancy, prints a warning to the screen & file. 
  52. 10/20/92 dgp Added VBL/frame to summary.
  53. 12/8/92    dgp Minor editing of comments.
  54. 12/9/92    dgp    1.01 Added _atexit(RestoreCluts) in case user quits prematurely.
  55. 12/11/92 dgp 1.02 GDRestoreBlackAndWhite allocates stack space for linearTable
  56.             only if we need it.
  57. 12/30/92 dgp 1.03 cosmetic changes.
  58. 12/30/92 dgp 1.04 Enhanced summary to account for NAN when no clut access is allowed.
  59. 12/30/92 dgp 1.05 Use GDClutSize().
  60. 1/6/93    dgp     1.06 Simplified GDRestoreBlackAndWhite, eliminated linearTable.
  61. 1/7/93    dgp  1.07 Included in VideoToolbox-1-93.sea
  62. 1/8/93    dgp     1.08 Added Info-Mac to report file.
  63. 1/11/93    dgp     1.09 In response to bug report from jonathan brecher, added support
  64.             for computers that lack Color QuickDraw.
  65. 1/15/93    dgp    1.10 Added version resource.
  66. 1/18/93    dgp    1.11 Updated the explanatory text.
  67. 1/24/93 dgp    1.12 Updated the explanatory text.
  68. 2/6/93 dgp    1.13 Report ROM version.
  69. 2/15/93 dgp    1.14 Report ROM version as 124+6*256.
  70. 2/22/93    dgp    1.15 Recompiled.
  71. 2/27/93    dgp    1.16 Recompiled with new Identify.c.
  72. 3/10/93    dgp    1.17 Choose a valid mode for which to print the frame rate.
  73.                  Moved all the timing code into GDInfo.c, so this routine just prints.
  74. 3/12/93    dgp        Reorganized the printout and added TestCluts's capabilities.
  75. 3/13/93    dgp        Added high-priority timing.
  76. 3/16/93    dgp    2.0 Added visual hash inspection.
  77. 3/31/93    dgp    2.1 Release version, clut test seems to work.
  78. 4/5/93    dgp    2.2 Recognize gray1 error.
  79. 4/6/93    dgp    2.3 Remove assumption that mode numbers imply certain number of bits.
  80. 4/13/93    dgp    2.3.1 Only set results file type when it is created, not when appending.
  81. 4/15/93    dgp        Display Testing window with gray wedge on test screen.
  82. 4/16/93    dgp    2.4b Restore compatibility with 1-bit qd.
  83. 4/19/93    dgp    2.4.1b Support old Mac II 24-bit NuBus addressing. Fixed two places
  84.                 where I used garbage in place of a linear color table when
  85.                 gdType==directType. Use new GDNewLinearColorTable.
  86. 4/19/93    dgp    2.4.2b Estimate color transformation matrix.
  87. 4/19/93    dgp    2.4.3b Use color transformation matrix to assess gray error.
  88. 4/19/93    dgp    2.4.4b Fixed RectToAddress to always return a 32-bit address,
  89.                     so it won't crash under System 6.
  90. 4/25/93    dgp    2.4.5b Correct for -0.5 bias in estimating rgb gains in TestClut.c
  91. 4/25/93    dgp    2.4.6b Test both color and gray modes; tolerance=3 lsb.
  92. 4/25/93    dgp    2.4.7b Worked around New Palette Manager bugs in original 32-bit QD.
  93. 4/27/93    dgp    2.4.8b Fixed stale-cache bug in SetPixelsQuickly. Cleaned up mode
  94.                     indexing.
  95. 4/28/93    dgp 2.4.9b Rewrote code dealing with SetDepth and HasDepth, wrote GDHasMode().
  96.                     Ignore "isGray" for 16 and 32-bit modes.
  97. 5/11/93    dgp 2.4.9bb Fixed bug in GDTestClutHash in GDTestClut.c in response to bug 
  98.                 report by Jonathan Brecher.
  99. 5/17/93    dgp 2.4.9bbb Changed SetEntriesQuickly to respect the color/gray mode of device.
  100. 5/18/93    dgp 2.4.9b4 Check that card->device is not NULL.
  101. 5/18/93    dgp 2.4.9b5 Suppress printing of identity color transformation.
  102. 5/18/93    dgp 2.4.9b6 Note that 16 and 32 bit depths are always in color mode.
  103. 5/18/93    dgp 2.5 Release version.
  104. 6/2/93    dgp 2.5.1 Updated text reference to info-mac.
  105. 7/7/93    dgp 2.5.2 Recompiled with new GDDacSize, for compatibility with
  106.                 Radius PowerView.
  107. 1/13/94    dgp 2.5.3 Updated description of VideoToolbox.
  108. 6/7/94    dgp    3.0.0 New C-based version of CopyBitsQuickly. Fixed versions of CopyBitsQuickly,
  109.                 SetPixelsQuickly, and SetEntriesQuickly that are compatible with dirty ROMs.
  110. 6/14/94    dgp    3.0.1 Copy Monaco-font style resource from application into the "TimeVideo report"
  111.                 document.
  112. 6/19/94    dgp    3.0.2 Various cosmetic changes, recompile with latest CopyBitsQuickly and 
  113.                 SetEntriesQuickly.
  114. 7/8/94    dgp    3.0.3 Recompiled with new THINK C 7.03.
  115. 7/29/94 dgp Eliminated use of "#s" printf format, since it's not supported by
  116.             Metrowerks CodeWarrior C.
  117. 8/1/94    dgp 3.0.4 Recompiled with latest sources.
  118. 8/14/94    dgp Added workaround for [][][][!isGray] bug in Metrowerks CodeWarrior compiler.
  119. 9/5/94 dgp removed assumption in printf's that int==short.
  120. 10/12/94 dgp Used Metrowerks CodeWarrior C to produce fat binary.
  121. 10/20/94 dgp 3.5 Defined BLANKLINE in GDInfo.h to work around bug in 
  122.         Metrowerks CW4.5 bug in handling of \r.
  123. 11/17/94    dgp    3.6 updated the text describing VideoToolbox.
  124. 11/17/94    dgp 3.6.1 added explanation of “GDSetEntries ... NAN ... frames”.
  125. 11/30/94    dgp Use Display Manager to test all display modes. Untested.
  126. 12/29/94 dgp 3.6.2 WriteAndReadClut now waits for vbl before reading.
  127. 1/7/95    dgp 3.6.3 Got Display Manager calls to work.
  128. 3/18/94 dgp 3.6.4 Recompiled with CodeWarrior 5.5, which supposedly fixs some C bugs.
  129. 3/22/95 dgp 3.6.5 Added check for bad Dome driver, and enhanced detection of CW compiler version.
  130. 4/1/95 dgp Updated my address. Added advice about how to interpret cscSetEntries/cscGetEntries 
  131.             errors.
  132. 4/7/95 dgp 3.6.6 Read A7 draft of "Designing Cards & Drivers for Power Mac Computers", and
  133.             added support for multiple resolutions on PCI Macs. 
  134. 4/8/95 dgp 3.7.0 Polished the explanatory text that appears at the beginning of the results file.
  135.             Disabled WAIT_FOR_VBL in WriteAndReadClut() in GDTestClut.c.
  136. 4/18/95 dgp Call SVersion() only if _SlotManager trap is available.
  137. 5/27/95 dgp cosmetic corrections. Print blank line at beginning of PrintVideoInfo.
  138.             Recompile with CodeWarrior 6.
  139. 6/14/95 dgp 3.7.2 recompile with new less-wordy version of IdentifyMachine().
  140. 6/24/95 dgp 3.7.3 updated URL from info-mac/dev/src/ to info-mac/dev/lib/.
  141. 9/9/95 dgp updated URL from ...372 to ...373.
  142. 9/18/95 dgp deleted Syracuse address.
  143. 10/5/95 dgp 3.7.4 recompiled with new GDVideo.c:GDCardName that checks for Slot Manager first, for
  144.         compatibility with PCI Macs.
  145. 10/6/95 dgp changed default to normally NOT do the visual tests.
  146. 10/14/95 dgp fixed bug reported by Brian McElree and Beau Watson whereby requesting tests at
  147.             multiple resolutions on a PCI Mac resulted in no testing at all. The bug was due
  148.             to my erroneous assumption that PCI Macs would not have a Slot Manager. 
  149.             Now scan PCI device list even if the Slot Manager is present. Recompiled with 
  150.             new Identify.c functions that work on PCI bus (they call UseNameRegistery.c).
  151. 10/24/95 dgp dropped ".fat" from name of application. Now prefer the mirror.apple.com ftp site.
  152. 10/25/95 dgp added Caution about multiple resolutions.
  153. 3/7/96 dgp Fixed TestCLUT.c to reflect fact that cscSetEntries reads from table[0],.... whereas 
  154.         cscGetEntries writes to table[start],.... 
  155. 3/7/96 dgp 3.7.5 note whether VM, RAMDoubler, or FileSharing is on.
  156. 3/26/96 dgp 3.7.6 increase tolerance for recognizing identity, allow insignificant bits to be garbage.
  157.             Changed "notIsGray" to "!isGray". This was an obsolete work-around for a compiler bug.
  158. 5/28/96    dgp    added conditional UNIVERSAL_INTERFACES_VERSION<0x0212.    
  159. 1/14/97    dgp    3.7.7 recompiled with CodeWarrior 10.    
  160. 3/19/97    dgp    3.7.8 recompiled. We no longer use Timer.c. Increased heap and stack.
  161. 4/10/97    dgp    Eliminate stuff that was conditional on !UNIVERSAL_HEADERS.
  162. */
  163. #define VERSION "3.7.8"
  164. #include "VideoToolbox.h"
  165. #ifndef __TRAPS__
  166.     #include <Traps.h>
  167. #endif
  168. #include <ROMDefs.h>    // catDisplay,typeVideo
  169. #include "GDInfo.h"
  170. void TimeVideo(void);
  171. char *IdentifyMachineAndType(void);
  172. void PrintVideoInfo(FILE *o[2],VideoInfo *card);
  173. void ReportRGBGains(FILE *o[2],Boolean quickly,Boolean isGray,VideoInfo *card);
  174. long GDColors(GDHandle device);
  175. char setEntriesString[][18]={"cscSetEntries","SetEntriesQuickly"};
  176. char colorGrayString[][6]={"color","gray"};
  177. char ColorGrayString[][6]={"Color","Gray"};
  178. #ifndef __DISPLAYS__
  179.     #include <Displays.h>
  180. #endif
  181.  
  182. /* 
  183. These enum definitions for use with the cscGetNextResolution driver status call 
  184. are taken from Apple's Designing PCI Cards and Drivers for
  185. Power Mac Computers. These enum definitions ought to be in Video.h, 
  186. but they aren't, as of version 2.1 (ETO18). They ARE in version 2.12 (ETO20).
  187. UNIVERSAL_INTERFACES_VERSION is defined in Apple's ConditionalMacros.h, but only
  188. since version 2.1, which was distributed with CodeWarrior 8.
  189. */
  190. #if UNIVERSAL_INTERFACES_VERSION<0x0212
  191.     enum{
  192.         kDisplayModeIDCurrent=0
  193.         ,kDisplayModeIDInvalid=0xffffffff
  194.         ,kDisplayModeIDFindFirstResolution=0xfffffffe
  195.         ,kDisplayModeIDNoMoreResolutions=0xfffffffd
  196.     };
  197. #endif
  198.  
  199. void SaveAndRestoreDevice(GDHandle device);
  200. OSErr GDSetDelay(GDHandle device,Boolean dontWaitForVBL,double nanoseconds);
  201. OSErr GDGetDelay(GDHandle device,Boolean *dontWaitForVBLPtr,double *nanosecondsPtr);
  202.  
  203. void main(void)
  204. {
  205.     StackGrow(40000L+MAX_SCREENS*sizeof(VideoInfo));
  206.     Require(gestaltOriginalQD);
  207.     TimeVideo();
  208. }
  209.  
  210. // new routines in Identify.c, based on Apple's new NameRegistry
  211. void GetCPUProperties(char *cpuName,long *cpuHz,Boolean *hasL2Cache);
  212. void GetVideoProperties(GDHandle device,char *slotName,char *cardName,char *cardModel);
  213.  
  214. void TimeVideo(void)
  215. {
  216.     long system;
  217.     static unsigned long v[256],colorMax;
  218.     FILE *o[2],*dataFile;
  219.     int newDataFile;
  220.     unsigned long time;
  221.     Str255 todayStr;
  222.     int i,j,d,error;
  223.     int cards,width,height;
  224.     int quickly,isGray,doTest,ok;
  225.     Rect r,screenRect;
  226.     WindowPtr window;
  227.     VideoInfo *card;
  228.     Handle rsrc;
  229.     int rsrcFile;
  230.     long value;
  231.     SpBlock spBlock;
  232.     short flags;
  233.     static char string[1024],datafilename[]="TimeVideo results";
  234.     Boolean doVisualTests,displayMgrOk,slotMgrPresent,testMultipleResolutions;
  235.     
  236.     assert(StackSpace()>5000);
  237.     MaximizeConsoleHeight();
  238.     #if (THINK_C || THINK_CPLUS || SYMANTEC_C)
  239.         console_options.title="\pTimeVideo";
  240.     #endif
  241.     printf("\n");    // ask THINK C to initialize quickdraw
  242.  
  243.     if(0){ // just for debugging.
  244.         GDHandle device=NULL;
  245.         char cpuName[128];
  246.         long cpuHz;
  247.         Boolean hasL2Cache;
  248.         char slotName[128],cardName[128],cardModel[128];
  249.  
  250.         GetCPUProperties(cpuName,&cpuHz,&hasL2Cache);
  251.         printf("%s, %ld MHz, cache?=%d\n",cpuName,cpuHz/1000000,hasL2Cache);
  252.         for(i=0;;i++){
  253.             device=GetScreenDevice(i);
  254.             if(device==NULL)break;
  255.             GetVideoProperties(device,slotName,cardName,cardModel);
  256.             printf("%s, %s, %s\n",slotName,cardName,cardModel);
  257.             printf("%s\n",IdentifyVideo(device));
  258.         }
  259.         printf("%s\n",BreakLines(IdentifyMachine(),78));
  260.         IdentifyMachine();
  261.         exit(1);
  262.     }
  263.     GetDateTime(&time);
  264.     srand(time);
  265.     srandU(time);
  266.     printf("%s","Welcome to TimeVideo " VERSION "\n");
  267.     printf("%s\n\n",BreakLines(IdentifyMachine(),78));
  268.     sprintf(string,
  269.     "This program will thoroughly test all your video devices and save the "
  270.     "results in the text file “%s”. Don’t be alarmed by the strange "
  271.     "antics of your screens. Everything will soon be back to normal. Just sit back "
  272.     "and enjoy the show.",datafilename);
  273.     sprintf(string,"%s You may quit at any time by hitting Command-period.\n",string);
  274.     printf(BreakLines(string,78));
  275.     if(1)printf(BreakLines("\n"
  276.         "TimeVideo times everything: the video frames, interrupts, and cluts. And it "
  277.         "determines what fraction of the screen you can fill with a real-time movie shown "
  278.         "by CopyBits() or CopyBitsQuickly(). Then it does write-then-read tests of the "
  279.         "clut, to make sure the video hardware and software "
  280.         "are working correctly.\n",78));
  281.  
  282.     if(1){    // Adjust the PowerMac 7500/8500 video driver CLUT timing
  283.         GDHandle device=NULL;
  284.         double nanoseconds=999;
  285.         Boolean adjust=0,dontWaitForVBL=-1;
  286.  
  287.         device=GetScreenDevice(0);
  288.         error=GDGetDelay(device,&dontWaitForVBL,&nanoseconds);
  289.         if(!error){
  290.             printf("\n%s\n",IdentifyVideo(device));
  291.             printf("[GDGetDelay:CLUT timing: dontWaitForVBL=%d, delay %.0f ns per rgb triplet]\n",(int)dontWaitForVBL,nanoseconds);
  292.             adjust=Choose(0,"Would you like to adjust the CLUT timing of this video driver?\n",noYes,2);
  293.         }else adjust=0;
  294.         if(adjust){
  295.             dontWaitForVBL=!Choose(1,"Wait for VBL when loading CLUT?\n",noYes,2);
  296.             nanoseconds=800;
  297.             printf("How many ns delay per RGB triplet? (%.0f)",nanoseconds);
  298.             gets(string);
  299.             sscanf(string,"%lf",&nanoseconds);
  300.             error=GDSetDelay(device,dontWaitForVBL,nanoseconds);
  301.             if(error)printf("GDSetDelay error %d\n",(int)error);
  302.             if(1){
  303.                 error=GDGetDelay(device,&dontWaitForVBL,&nanoseconds);
  304.                 if(error)printf("GDGetDelay error %d\n",(int)error);
  305.                 else printf("[GDGetDelay:CLUT timing: dontWaitForVBL=%d, delay %.0f ns per rgb triplet]\n"
  306.                     "These settings will persist until you restart your computer.\n"
  307.                     ,(int)dontWaitForVBL,nanoseconds);
  308.             }
  309.         }
  310.     }
  311.     doVisualTests=Choose(0,"\nWould you like the testing to pause for visual inspections?\n",noYes,2);
  312.     value=0;
  313.     Gestalt(gestaltDisplayMgrAttr,&value);
  314.     displayMgrOk=value&(1<<gestaltDisplayMgrPresent);
  315.     if(displayMgrOk)
  316.         printf("%s",BreakLines("\n(Caution: testing multiple resolutions may fail, especially if the video driver " 
  317.         "offers more than seven resolutions, leaving your display in "
  318.         "whatever resolution was then being tested. I hope to fix this in the next version. dgp.)\n",78));
  319.     if(displayMgrOk)
  320.         testMultipleResolutions=Choose(0,"Test multiple resolutions of each display?\n",noYes,2);
  321.     else testMultipleResolutions=0;
  322.  
  323.     printf("\n");
  324.     dataFile=fopen(datafilename,"r");
  325.     newDataFile=(dataFile==NULL);
  326.     if(newDataFile){
  327.         // try to copy SimpleText style resource from TimeVideo application into our document.
  328.         rsrc=GetResource('styl',128);
  329.         if(rsrc!=NULL){
  330.             c2pstr(datafilename);
  331.             CreateResFile((unsigned char *)datafilename);
  332.             rsrcFile=OpenResFile((unsigned char *)datafilename);
  333.             p2cstr((unsigned char *)datafilename);
  334.             if(rsrcFile!=-1){
  335.                 DetachResource(rsrc);
  336.                 UseResFile(rsrcFile);
  337.                 AddResource(rsrc,'styl',128,(ConstStr255Param)"/pTimeVideo report style");
  338.                 CloseResFile(rsrcFile);
  339.             }else ReleaseResource(rsrc);
  340.         }
  341.     }else fclose(dataFile);
  342.     o[0]=stdout;
  343.     o[1]=dataFile=fopen(datafilename,"a");    /* Append to data file */
  344.     if(dataFile==NULL){
  345.         printf("Could neither open nor create “%s”. Perhaps your disk is locked.\n"
  346.             ,datafilename);
  347.         newDataFile=0;
  348.     }
  349.     if(newDataFile){
  350.         SetFileInfo(datafilename,'TEXT','ttxt');
  351.         fprintf(dataFile,BreakLines(
  352.         "This file reports the timing and accuracy of all your video screens, "
  353.         "as measured by TimeVideo, a component of the VideoToolbox. "
  354.         "TimeVideo runs on all Macs, requiring only System 6.05 or better. To quickly "
  355.         "test a large number of computers, run TimeVideo from a floppy disk; "
  356.         "all the results will accumulate in a single results file. TimeVideo is "
  357.         "free, and may be freely distributed. You can get the latest version from:\n"
  358.         "<http://rajsky.psych.nyu.edu/VideoToolbox/Download.html>\n"
  359.         "\nTHE VIDEO TOOLBOX\n"
  360.         "The VideoToolbox is a collection of two hundred C subroutines and "
  361.         "several demo and utility programs that I and others have written to do "
  362.         "visual psychophysics with Macintosh computers. It is fully compatible "
  363.         "with 680x0 and PowerPC Macs and with Metrowerks CodeWarrior C and "
  364.         "Symantec C compilers. It's free and may not be sold without "
  365.         "permission. It should be useful to anyone who wants to present "
  366.         "accurately specified visual stimuli or use the Mac for psychometric "
  367.         "experiments. Most of the associated documentation is at the VideoToolbox "
  368.         "web site: \n"
  369.         "<http://rajsky.psych.nyu.edu/VideoToolbox/>\n"
  370.         "At the web site, the \"Video synch\" page discusses all the ways of "
  371.         "synchronizing programs to video displays and the many pitfalls to avoid. "
  372.         "The TimeVideo application checks out the timing of all video devices in "
  373.         "anticipation of their use in critical real-time applications, e.g. "
  374.         "movies or lookup table animation. Low-level routines control video "
  375.         "timing and lookup tables, display real-time movies, and implement the "
  376.         "luminance-control algorithms suggested by Pelli and Zhang (1991). (D.G. "
  377.         "Pelli and L. Zhang, 1991, \"Accurate control of contrast on "
  378.         "microcomputer displays.\" Vision Research, 31, 1337-1350. Reprints are "
  379.         "available.) In particular, GetPixelsQuickly and SetPixelsQuickly peek "
  380.         "and poke pixels in bitmaps and pixmaps, CopyBitsQuickly and CopyWindows "
  381.         "faithfully copy between bit/pixmaps and the screen, WindowToEPS saves an "
  382.         "image to disk, for later printing or incorporation into a document, and "
  383.         "SetEntriesQuickly and GDSetEntries load the screen's color lookup table, "
  384.         "all without any of QuickDraw's color translations. High-level routines "
  385.         "help analyze psychophysical experiments (e.g. graphing or "
  386.         "maximum-likelihood fitting of psychometric data). Assign.c is a runtime "
  387.         "C interpreter for C assignment statements, which is useful for "
  388.         "controlling experiments and sharing calibration data. "
  389.         "Many of the routines are Mac-specific, but some very "
  390.         "useful routines, e.g. the luminance-control, statistics, "
  391.         "maximum-likelihood fitting algorithms, and the runtime interpreter are "
  392.         "written in Standard C and will work on any computer. "
  393.         "Documentation is in the source files themselves. "
  394.         "This collection has been continually updated since 1991. "
  395.         "You can download the latest version from: "
  396.         "<http://rajsky.psych.nyu.edu/VideoToolbox/Download.html>\n"
  397.         "Or search for \"video-toolbox\" in a public archive, e.g.:\n"
  398.         "<http://hyperarchive.lcs.mit.edu/HyperArchive/SearchForm.html>\n"
  399.         "ftp://ftp.stolaf.edu/pub/macpsych/\n"
  400.         "\n",78));
  401.     fprintf(dataFile, "%s\n\n",
  402.         BreakLines("Denis Pelli, denis@psych.nyu.edu, "
  403.         "Psychology Dept., New York University, "
  404.         "6 Washington Place, New York, NY 10003, USA",78));
  405.     fprintf(dataFile,BreakLines(
  406.         "\nTIME VIDEO\n"
  407.         "Other than reading and writing pixels, all access to a video device normally goes "
  408.         "through the software video driver. (You can use VideoToolbox SetEntriesQuickly.c "
  409.         "to bypass the video driver of a few devices, but I don't recommend that, except "
  410.         "as a last resort, because your program will then only work with the few video "
  411.         "devices that SetEntriesQuickly.c supports.) The video driver is normally supplied "
  412.         "in ROM on the video card or, for built-in video, in the computer's ROM. "
  413.         "(New PCI drivers can simply be tossed onto the System Folder.) Many "
  414.         "drivers have subtle bugs that TimeVideo has uncovered and documented. A complete "
  415.         "list of all known video driver bugs appears in the \"Video bugs\" page of "
  416.         "the VideoToolbox web site mentioned above. Please send new bugs to "
  417.         "denis@psych.nyu.edu\n\n"
  418.         "For each video card, TimeVideo measures the video frame rate, frequency of VBL "
  419.         "interrupts (ought to be one per frame), how long it takes to load the clut "
  420.         "(ought to be one frame or less), "
  421.         "and how much of the screen you can fill with a real-time one-image-per-frame "
  422.         "movie shown by CopyBits() or CopyBitsQuickly(). It then performs a random "
  423.         "write-then-read test of the Color Lookup Table (clut). This tests the clut "
  424.         "memory hardware and the software used to write and read the clut. We test "
  425.         "writing by GDSetEntries(), which makes a cscSetEntries call to the video driver, and, "
  426.         "if possible, we also test writing by SetEntriesQuickly(), which accesses the hardware "
  427.         "directly. (SetEntriesQuickly supports only a few video cards.) "
  428.         "In either case, the clut is read by GDGetEntries, "
  429.         "which makes a cscGetEntries call to the video driver. "
  430.         "The testing is thorough; many video devices fail "
  431.         "at least part of the test. All the driver errors uncovered to date appear in the "
  432.         "VideoToolbox \"Video bugs\" text file, and have been reported to the video card's "
  433.         "manufacturer. Add your results by emailing this file to "
  434.         "denis@psych.nyu.edu\n"
  435.         "\nHappily, we don't know of any cases of errors in cscSetEntries itself, "
  436.         "and we've never run across a hardware failure of the CLUT memory. "
  437.         "Our experience is that errors in the cscSetEntries/cscGetEntries write-then-read "
  438.         "test of the CLUT occur in two ways: "
  439.         "\n1. The driver does not implement cscSetGamma and the fixed gamma table is "
  440.         "not the identity transformation, i.e. \"uncorrected gamma table\". "
  441.         "TimeVideo always calls cscSetGamma, requesting an identity transform. "
  442.         "In all drivers, the RGB values supplied in a cscSetEntries call "
  443.         "are transformed by the gamma table before being loaded into the CLUT. "
  444.         "The presence of a non-identity "
  445.         "gamma table is usually obvious from a glance at the color matrix, since the gains "
  446.         "in the matrix will exceed 1 by about twice the fractional rms error indicated by ±%%."
  447.         "\n2. The CLUT is set correctly, but the driver incorrectly implements cscGetEntries. "
  448.         "Bugs in cscGetEntries are usually obvious from a glance at the color matrix, "
  449.         "e.g. nonzero gains off the diagonal, or the pattern of errors.\n\n"
  450.  
  451.         "The video driver on the PowerMac 7500 and 8500 supresses the VBL interrupt while it's "
  452.         "loading the CLUT, so any VBL-frame-counting task will miss an interrupt each time "
  453.         "you call cscSetEntries to load the clut. Calling cscSetEntries once per frame to "
  454.         "synchronize your code with the frame should still work fine, but it'll be slightly tricky "
  455.         "to check for overrun (i.e. taking too long between cscSetEntries calls), since a "
  456.         "simple VBL-interrupt-based frame counter will run differently on different Macs.\n\n"
  457.  
  458.         "Errors reported by TimeVideo are usually due to bugs in the video driver software "
  459.         "in the ROM of the video card (or built-in video device). If the bug will "
  460.         "interfere with your experiments, "
  461.         "then to use the card (or built-in video) you must either fix/replace the driver or "
  462.         "bypass the driver, using SetEntriesQuickly to access the hardware directly "
  463.         "(if SetEntriesQuickly supports it). "
  464.         "I suggest that you try replacing the driver, because this will keep your "
  465.         "software hardware-independent. The VideoToolbox \"Video synch\" document explains "
  466.         "how to fix the the Mac IIci video driver, patching or replacing the buggy version "
  467.         "0 .Display_Video_Apple_RBV1 driver by copying the bug-free version 1 of the same "
  468.         "driver from the Mac IIsi. It is very likely that an analogous approach "
  469.         "could be used to fix/replace the buggy version 0, 1, and 2 .Display_Video_Apple_DAFB "
  470.         "drivers in the Quadra 700, 750, and 900, by the bug-free version 3 or 5 of that driver "
  471.         "in the Centris 650 or the LC 475. And please report your bugs; the bug-free revised drivers "
  472.         "are probably the result of our past bug reports.\n"
  473.         "\nGLOSSARY\n"
  474.         "A video frame is a complete refresh of your video screen. (With interlacing it takes "
  475.         "two fields to do a complete refresh, i.e. a frame, but graphics displays usually are "
  476.         "not interlaced and have a single field per frame.) To show a movie, you will want "
  477.         "to reload the image once per frame; the table shows how big that image can be, "
  478.         "as a fraction of the screen area, and still be reloaded once per frame. "
  479.         "(Some video cards have multiple video \"pages\"--call GDGetPageCnt()--that "
  480.         "can be switched by calling GDSetPageDrawn() and GDSetPageShown().) "
  481.         "A \"VBL\" "
  482.         "interrupt is produced by your video card, nominally once per frame, but "
  483.         "some video cards produce more, which is poor, but not serious. (The "
  484.         "VBLInstall.c program will deal with it.) Suppressed interrupts during clut "
  485.         "updates are bad. It means that the driver disables the VBL interrupt for too "
  486.         "long while it's loading the clut. This will throw off any interrupt-based "
  487.         "attempt to count frames. (The clut is the color lookup table of your video card.) "
  488.         "Lookup table animation, e.g. for temporal modulation of contrast, requires that "
  489.         "you reload the clut once per frame, so it's very important that this be fast "
  490.         "enough. "
  491.         "The first call to SetEntriesQuickly() for each device is slow--a cache is "
  492.         "filled; the reported times are for subsequent calls. "
  493.         "Each video card can be in \"Color\" or \"Gray\" mode, as set by the Control "
  494.         "Panel:Monitors or the Macintosh Toolbox call SetDepth(). "
  495.         "In \"Gray\" mode all colors are transformed to luminance-equivalent "
  496.         "grays. This is done by the video driver, when loading the clut, "
  497.         "but only if the pixelSize≤8. "
  498.         "TimeVideo does a linear regression on the results of write-then-read tests of the CLUT "
  499.         "to measure the driver's color transformation matrix. It reports the matrix "
  500.         "if it is other than the identity transformation:\n"
  501.         "(ROut) (1 0 0) (RIn)\n"
  502.         "(GOut)=(0 1 0)x(GIn)\n"
  503.         "(BOut) (0 0 1) (BIn)\n"
  504.         "The clut tests try loading the clut serially, one entry at a time, and "
  505.         "all at once; some video drivers fail the serial test. For more explanation see "
  506.         "the text file called \"Video synch\" on the VideoToolbox disk.\n"
  507.         "\n"
  508.         "\"cscSetEntries ... NAN ... frames\"= the driver refuses to load the CLUT.\n" 
  509.         "\"ok\"= passed all tests.\n"
  510.         "\"!gray\"= passed the color test, but is supposed to be in gray mode.\n"
  511.         "\"!color\"= passed the gray test, but is supposed to be in color mode.\n"
  512.         "\"!serial\"= passed when loaded all at once, but failed when loaded serially.\n"
  513.         "\"bad\"= read did not equal write and the error is reported explicitly.\n"
  514.         "We know that SetEntriesQuickly is \"!serial\" on the Quadra, alas.\n"
  515.         "\nThis is a SimpleText document using the Monaco font to line up the \n"
  516.         "columns properly.\n\n",78));
  517.     }
  518.     ffprintf(o,"TimeVideo version " VERSION "\n");
  519.     GetDateTime(&time);
  520.     IUDateString(time,longDate,todayStr);
  521.     ffprintf(o,"%s.\n",p2cstr(todayStr));
  522.     ffprintf(o,"%s\n",IdentifyCompiler());
  523.     if(strlen(IdentifyVM())>0)ffprintf(o,"%s\n",IdentifyVM());
  524.     if(IsFileSharingOn())ffprintf(o,"File Sharing is on.\n");
  525.  
  526.     // SetDepth() is part of the "new" Palette Manager, which appeared in System 6.0.5.
  527.     Gestalt(gestaltSystemVersion,&system);
  528.     if(system<0x605)PrintfExit("Sorry. Your System is too old; I need at least System 6.0.5.\n");
  529.     card=(VideoInfo *)NewPtrClear(MAX_SCREENS*sizeof(*card));
  530.     if(card==NULL)PrintfExit("Need more memory. "
  531.         "Use Finder's File:Get Info to increase TimeVideo's allocation.\n");
  532.     ffprintf(o,"%s\n",BreakLines(IdentifyMachineAndType(),78));
  533.     if(o[1]!=NULL){
  534.         //fprintf(o[1],"Tick rate is %.1f Hz. ",TickRate());
  535.         //fprintf(o[1],"System-based VBL rate is %.1f Hz.\n",GDFrameRate(NULL));
  536.     }
  537.     if(QD8Exists())_atexit(RestoreCluts);// In case user quits prematurely.
  538.     if(TrapAvailable(_SlotManager))slotMgrPresent=(SVersion(&spBlock)==noErr);
  539.     else slotMgrPresent=0;
  540.     spBlock.spSlot=0;
  541.     spBlock.spID=0;
  542.     for(i=0;;i++){
  543.         if(QD8Exists()){
  544.             if(testMultipleResolutions){ // index through the display modes, one by one
  545.                 unsigned long currentDisplayModeID,depthModeUL,switchFlags;
  546.                 Boolean modeOk;
  547.                 unsigned long horizontalPixels,verticalLines;    // not used
  548.                 Fixed refreshRate;                                // not used
  549.                 unsigned short maxDepthMode;                    // not used
  550.                 static int deviceCounter=0;
  551.                 static unsigned long displayModeID=kDisplayModeIDFindFirstResolution;
  552.                 Boolean useSlotMgr;
  553.             
  554.                 useSlotMgr=slotMgrPresent;
  555.                 if(slotMgrPresent){                    
  556.                     spBlock.spExtDev=0;
  557.                     spBlock.spHwDev=0;
  558.                     spBlock.spCategory=catDisplay;
  559.                     spBlock.spCType=typeVideo;
  560.                 }
  561.             nextDisplayModeID:
  562.                 if(useSlotMgr){
  563.                     // NuBus or no bus: use slot manager to find every driver displayModeID
  564.                     spBlock.spTBMask=3;        // ignore DrvrHW and DrvrSW
  565.                     spBlock.spParamData=1;    // next resource, any slot, whether enabled or disabled
  566.                     error=SGetTypeSRsrc(&spBlock);
  567.                     if(error){
  568. //printf("%s %d: ",__FILE__,__LINE__);Choose(1,"Continue?\n",noYes,2);
  569.                         // done
  570.                         if(displayModeID!=kDisplayModeIDFindFirstResolution)
  571.                             SaveAndRestoreDevice(NULL);
  572.                         useSlotMgr=0;
  573.                         goto checkPCIBus;
  574.                     }
  575.                     displayModeID=(unsigned char)spBlock.spID;    // "resource reference number"
  576.                     if(spBlock.spRefNum==0){
  577.                         spBlock.spTBMask=2;        // ignore DrvrSW
  578.                         spBlock.spParamData=2;    // find enabled resource in same slot with same DrvrHW
  579.                         spBlock.spID=0;
  580.                         error=SGetTypeSRsrc(&spBlock);
  581.                         if(error)goto nextDisplayModeID;
  582.                         spBlock.spID=displayModeID;    // restore
  583.                     }
  584.                     card[i].device=GetDeviceByRefNum(spBlock.spRefNum);
  585.                     SaveAndRestoreDevice(card[i].device);
  586.                     if(card[i].device==NULL)goto nextDisplayModeID;
  587.                 }
  588.                 checkPCIBus:
  589.                 if(!useSlotMgr){
  590.                     // PCI bus: use new driver calls to test each displayModeID of each driver
  591.                     card[i].device=GetScreenDevice(deviceCounter);
  592.                     SaveAndRestoreDevice(card[i].device);
  593.                     if(card[i].device==NULL)break;    // no more video devices; go home.
  594.                     error=GDGetNextResolution(card[i].device,displayModeID
  595.                         ,&displayModeID,&horizontalPixels,&verticalLines
  596.                         ,&refreshRate,&maxDepthMode);
  597.                     if(error || displayModeID==kDisplayModeIDNoMoreResolutions
  598.                         || displayModeID==kDisplayModeIDInvalid){
  599.                         deviceCounter++;
  600.                         displayModeID=kDisplayModeIDFindFirstResolution;
  601.                         // error merely indicates that driver doesn't support the Display Manager
  602.                         if(error)goto testDeviceAnyway;
  603.                         goto nextDisplayModeID;
  604.                     }
  605.                 }
  606.                 error=DMCheckDisplayMode(card[i].device,displayModeID,firstVidMode,&switchFlags,0,&modeOk);
  607.                 if(!modeOk)goto nextDisplayModeID;
  608.                 error=GDGetDisplayMode(card[i].device,¤tDisplayModeID,NULL,NULL,NULL);
  609.                 if(error || currentDisplayModeID!=displayModeID){
  610.                     depthModeUL=firstVidMode;
  611.                     error=DMSetDisplayMode(card[i].device,displayModeID,&depthModeUL,0,NULL);
  612.                     if(error==kDMDriverNotDisplayMgrAwareErr)goto testDeviceAnyway;
  613.                     if(error)goto nextDisplayModeID;
  614.                 }
  615.             testDeviceAnyway:;
  616.             }else{
  617.                 // index through the video devices
  618.                 card[i].device=GetScreenDevice(i);
  619.                 SaveAndRestoreDevice(card[i].device);
  620.                 if(card[i].device==NULL)break;
  621.             }
  622.         }else card[i].device=NULL;
  623.         ffprintf(o,"\n%s\n",BreakLines(IdentifyVideo(card[i].device),78));
  624.         if(card[i].device==GetMainDevice()){
  625.             // Print out info from special call for PowerMac 7500/8500 built-in video driver
  626.             double nanoseconds;
  627.             Boolean dontWaitForVBL;
  628.  
  629.             error=GDGetDelay(card[i].device,&dontWaitForVBL,&nanoseconds);
  630.             if(!error)ffprintf(o,"[GDGetDelay:CLUT timing: dontWaitForVBL=%d, delay %.0f ns per rgb triplet]\n",(int)dontWaitForVBL,nanoseconds);
  631.         }
  632.         printf("Getting card info. . ." "\r");
  633.         error=GDInfo(&card[i]);
  634.         
  635.         // Specify what tests we want.
  636.         for(d=0;d<6;d++)for(quickly=0;quickly<2;quickly++)for(isGray=0;isGray<2;isGray++){
  637.             card[i].depth[d].clut[quickly][isGray].read.doTest=1;
  638.         }
  639.         if(card[i].device!=NULL)isGray=!TestDeviceAttribute(card[i].device,gdDevType);
  640.         else isGray=0;
  641.         // Further test deepest directType and clutType modes
  642.         d=5;
  643.         while(card[i].depth[d].pixelSize>=0 && card[i].depth[d].pixelSize<16)d--;
  644.         if(d>=0)for(quickly=0;quickly<2;quickly++){
  645.             card[i].depth[d].clut[quickly][isGray].hash.doTest=doVisualTests;
  646.             card[i].depth[d].clut[quickly][!isGray].read.doTest=1;
  647.         }
  648.         d=3;
  649.         while(card[i].depth[d].pixelSize==0 || card[i].depth[d].pixelSize>8)d--;
  650.         if(d>=0)for(quickly=0;quickly<2;quickly++){
  651.             card[i].depth[d].clut[quickly][isGray].hash.doTest=doVisualTests;
  652.             card[i].depth[d].clut[quickly][!isGray].read.doTest=1;
  653.         }
  654.  
  655.         // Open window
  656.         if(QD8Exists()){
  657.             r=screenRect=(*card[i].device)->gdRect;
  658.         }else{
  659.             CopyQuickDrawGlobals();    // make sure qd is valid.
  660.             r=screenRect=qd.screenBits.bounds;
  661.         }
  662.         OffsetRect(&r,-r.left,-r.top);
  663.         width=(r.right/=3);
  664.         if(width>256)width=r.right=256;
  665.         height=r.bottom=width;
  666.         CenterRectInRect(&r,&screenRect);
  667.         if(QD8Exists())window=(WindowPtr)NewCWindow(NULL,&r,"\pCLUT Colors",TRUE,noGrowDocProc,(WindowPtr) -1L,0,0);
  668.         else window=NewWindow(NULL,&r,"\pTesting",TRUE,noGrowDocProc,(WindowPtr) -1L,0,0);
  669.             
  670.         for(d=0;d<6;d++)for(isGray=0;isGray<2;isGray++){
  671.             if(card[i].depth[d].pixelSize==0)continue;
  672.             doTest=0;
  673.             for(quickly=0;quickly<2;quickly++){
  674.                 doTest|=card[i].depth[d].clut[quickly][isGray].read.doTest;
  675.                 doTest|=card[i].depth[d].clut[quickly][isGray].hash.doTest;
  676.                 doTest|=card[i].depth[d].clut[quickly][isGray].visual.doTest;
  677.             }
  678.             if(!doTest)continue;
  679.             if(card[i].device!=NULL){
  680.                 if(card[i].depth[d].mode!=(**card[i].device).gdMode
  681.                     || isGray!=!TestDeviceAttribute(card[i].device,gdDevType)){
  682.                     // On Mac IIci, Sys 6.07, HasDepth returns "mode" of 0x100 
  683.                     // at all legal depths.
  684.                     if(card[i].depth[d].pixelSize>0)
  685.                         ok=HasDepth(card[i].device,card[i].depth[d].pixelSize,1,!isGray);
  686.                     else ok=1;
  687.                     // Mac IIci Sys 6.07: SetDepth only accepts mode, not pixelSize.
  688.                     if(ok)error=SetDepth(card[i].device,card[i].depth[d].mode,1,!isGray);
  689.                     if(card[i].depth[d].mode!=(**card[i].device).gdMode
  690.                         || isGray!=!TestDeviceAttribute(card[i].device,gdDevType))continue;
  691.                 }
  692.             }else if(card[i].depth[d].pixelSize!=1 || isGray)continue;
  693.             GDInfo(&card[i]);
  694.             
  695.             // Fill window with spectrum of all colors.
  696.             colorMax=GDColors(card[i].device)-1;
  697.             for(j=0;j<width;j++)v[j]=(j*colorMax+width/2)/(width-1);
  698.             for(j=0;j<height;j++)SetWindowPixelsQuickly(window,0,j,v,width);
  699.             
  700.             if(!card[i].depth[d].timeTested){
  701.                 error=GDInfoTime(&card[i]);
  702.                 for(j=0;j<height;j++)SetWindowPixelsQuickly(window,0,j,v,width);//refresh
  703.             }
  704.             for(quickly=0;quickly<2;quickly++){
  705.                 printf(BLANKLINE);
  706.                 printf("%d-bit %s pixels: testing clut: %s . . ." "\r"
  707.                     ,(int)card[i].depth[d].pixelSize,colorGrayString[isGray]
  708.                     ,setEntriesString[quickly]);
  709.                 if(quickly)flags=testClutQuicklyFlag;
  710.                 else flags=0;
  711.                 error=GDTestClut(o,flags,&card[i]);
  712.                 error=GDTestClut(o,flags|testClutSeriallyFlag,&card[i]);
  713.                 error=GDTestClutHash(flags,&card[i]);
  714.             }
  715.         }
  716.         DisposeWindow(window);
  717.         if(QD8Exists()){
  718.             // On Mac IIci, Sys 6.07, HasDepth returns "mode" of 0x100 at all legal depths.
  719.             // and SetDepth only accepts mode, not depth.
  720. //            ok=HasDepth(card[i].device,oldPixelSize,1,oldIsColor);
  721. //            if(ok)error=SetDepth(card[i].device,oldDepthMode,1,oldIsColor);
  722.         }
  723.         printf(BLANKLINE);
  724.         printf("Getting card info. . ." "\r");
  725.         error=GDInfo(&card[i]);
  726.         printf(BLANKLINE);
  727.         PrintVideoInfo(o,&card[i]);
  728.         if(dataFile!=NULL)fflush(dataFile);
  729.         if(kbhit())getcharUnbuffered();    // Give console a chance to detect Command-.
  730.         if(!QD8Exists())break;
  731.     }
  732.     cards=i;
  733.     if(GDVersion(card[i].device)==100
  734.         && EqualString("\p.Display_Video_Apple_RBV1",GDName(card[i].device),1,1)){
  735.             ffprintf(o,
  736.             "NOTE: the built-in driver in the Mac IIci (.Display_Video_Apple_RBV1 version 0)\n"
  737.             "has a bug that causes it to crash if you attempt to read the clut. A temporary\n"
  738.             "patch has been applied that has fixed the driver until the next reboot, as\n"
  739.             "explained in the VideoToolbox document \"Video synch\".\n");
  740.     }
  741.     if(dataFile!=NULL){
  742.         fprintf(dataFile,"\n\n");
  743.         fclose(dataFile);
  744.         printf(BreakLines("\nTimeVideo is free, and may be freely distributed. "
  745.             "You can get the latest version from:\n"
  746.             "<http://rajsky.psych.nyu.edu/VideoToolbox/Download.html>\n",78));
  747.         sprintf(string,"\nThe text file \"%s\" explains all the results.\n",datafilename);
  748.         printf(BreakLines(string,78));
  749.     }
  750.     DisposePtr((Ptr)card);
  751. }
  752.  
  753. void PrintVideoInfo(FILE *o[2],VideoInfo *card)
  754. {
  755.     short i,d,tested,quickly,isGray,grayClut,reportRGB;
  756.     char string[100];
  757.     static char s1[]="%-33s",s2[]="%6s",s3[]="  %-9s\n";
  758.     VideoCardClutTest *clut;
  759.     
  760.     printf("\r"    "          " "          " "          " "          "
  761.         "          " "          " "          " "          " "\r");
  762.     if(card->device!=NULL)isGray=!TestDeviceAttribute(card->device,gdDevType);
  763.     else isGray=0;
  764.     ffprintf(o,"%d-bit dacs. ",(int)card->dacSize);
  765.     ffprintf(o,"%dx%d pixels. ",(int)card->width,(int)card->height);
  766. //    ffprintf(o,"%s mode. ",ColorGrayString[isGray]);
  767.     ffprintf(o,"\n");
  768.  
  769.     if(card->basicTested){
  770.         ffprintf(o,s1,"pixel size");
  771.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  772.             sprintf(string,"%d   ",(int)card->depth[d].pixelSize);
  773.             ffprintf(o,s2,string);
  774.         }
  775.         ffprintf(o,s3,"bits");
  776.     
  777.         if(QD8Exists()){
  778.             // I haven't tried to look for 1-bit QD's page swapping scheme
  779.             ffprintf(o,s1,"pages");
  780.             for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  781.                 sprintf(string,"%d   ",(int)card->depth[d].pages);
  782.                 ffprintf(o,s2,string);
  783.             }
  784.             ffprintf(o,s3,"");
  785.     
  786.             // Meaningless in 1-bit QD
  787.             ffprintf(o,s1,"mode");
  788.             for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  789.                 sprintf(string,"0x%x ",card->depth[d].mode);
  790.                 ffprintf(o,s2,string);
  791.             }
  792.             ffprintf(o,s3,"");
  793.         }
  794.     }
  795.  
  796.     if(card->timeTested){
  797.         ffprintf(o,s1,"frame rate");
  798.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  799.             sprintf(string,"%.1f ",card->depth[d].frameRate);
  800.             ffprintf(o,s2,string);
  801.         }
  802.         ffprintf(o,s3,"Hz");
  803.     
  804.         ffprintf(o,s1,"interrupts per frame");
  805.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  806.             sprintf(string,"%.1f ",card->depth[d].vblPerFrame);
  807.             ffprintf(o,s2,string);
  808.         }
  809.         ffprintf(o,s3,"");
  810.     
  811.         ffprintf(o,s1,"CopyBits movie size");
  812.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  813.             sprintf(string,"%.2f",card->depth[d].movieRate/card->depth[d].frameRate);
  814.             ffprintf(o,s2,string);
  815.         }
  816.         ffprintf(o,s3,"screen");
  817.     
  818.         ffprintf(o,s1,"CopyBitsQuickly movie size");
  819.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  820.             sprintf(string,"%.2f",card->depth[d].movieRateQuickly/card->depth[d].frameRate);
  821.             ffprintf(o,s2,string);
  822.         }
  823.         ffprintf(o,s3,"screen");
  824.     
  825.         ffprintf(o,s1,"CopyBits data rate");
  826.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  827.             sprintf(string,"%.2f",card->depth[d].movieRate
  828.                 *card->depth[d].pixelSize
  829.                 *card->width*card->height/8./1024./1024.);
  830.             ffprintf(o,s2,string);
  831.         }
  832.         ffprintf(o,s3,"MB/s");
  833.     
  834.         ffprintf(o,s1,"CopyBitsQuickly data rate");
  835.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  836.             sprintf(string,"%.2f",card->depth[d].movieRateQuickly
  837.                 *card->depth[d].pixelSize
  838.                 *card->width*card->height/8./1024./1024.);
  839.             ffprintf(o,s2,string);
  840.         }
  841.         ffprintf(o,s3,"MB/s");
  842.     }
  843.     
  844.     if(card->device!=NULL && (**card->device).gdType!=fixedType && card->timeTested){
  845.         ffprintf(o,s1,"cscSetEntries duration");
  846.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  847.             sprintf(string,"%.2f",card->depth[d].framesPerClutUpdate);
  848.             ffprintf(o,s2,string);
  849.         }
  850.         ffprintf(o,s3,"frames");
  851.  
  852.         ffprintf(o,s1,"cscSetEntries suppresses ints.for");
  853.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  854.             sprintf(string,"%.1f ",card->depth[d].missingFramesPerClutUpdate);
  855.             ffprintf(o,s2,string);
  856.         }
  857.         ffprintf(o,s3,"frames");
  858.  
  859.         ffprintf(o,s1,"GDSetEntriesHighPriority duration");
  860.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  861.             sprintf(string,"%.2f",card->depth[d].framesPerClutUpdateHighPriority);
  862.             ffprintf(o,s2,string);
  863.         }
  864.         ffprintf(o,s3,"frames");
  865.  
  866.         if(card->setEntriesQuickly){
  867.             ffprintf(o,s1,"SetEntriesQuickly duration");
  868.             for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  869.                 sprintf(string,"%.2f",card->depth[d].framesPerClutUpdateQuickly);
  870.                 ffprintf(o,s2,string);
  871.             }
  872.             ffprintf(o,s3,"frames");
  873.         }
  874.     }
  875.     if(card->device!=NULL && (**card->device).gdType!=fixedType && card->clutTested){
  876.         for(quickly=0;quickly<2;quickly++)for(isGray=0;isGray<2;isGray++){
  877.             tested=0;
  878.             for(d=0;d<6;d++)if(card->depth[d].pixelSize)
  879.                 if(card->depth[d].clut[quickly][isGray].hash.tested)tested=1;
  880.             if(tested){
  881.                 sprintf(string,"%s hash inspection",setEntriesString[quickly]);
  882.                 ffprintf(o,s1,string);
  883.                 for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  884.                     clut=&card->depth[d].clut[quickly][isGray];
  885.                     if(clut->hash.tested){
  886.                         if(clut->hash.errors)
  887.                             sprintf(string,"fail");
  888.                         else sprintf(string,"ok");
  889.                     }else sprintf(string,"");
  890.                     ffprintf(o,s2,string);
  891.                 }
  892.                 ffprintf(o,s3,"");
  893.             }
  894.         }
  895.     
  896.         for(quickly=0;quickly<2;quickly++)for(isGray=0;isGray<2;isGray++){
  897.             tested=0;
  898.             for(d=0;d<6;d++)if(card->depth[d].pixelSize)
  899.                 if(card->depth[d].clut[quickly][isGray].read.tested)tested=1;
  900.             if(tested){
  901.                 reportRGB=0;
  902.                 sprintf(string,"%s: %s test"
  903.                     ,colorGrayString[isGray],setEntriesString[quickly]);
  904.                 ffprintf(o,s1,string);
  905.                 for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  906.                     clut=&card->depth[d].clut[quickly][isGray];
  907.                     grayClut=isGray && card->depth[d].pixelSize<=8;
  908.                     if(clut->read.tested){
  909.                         if(clut->read.errors){
  910.                             if(clut->read.errorsAtOnce)sprintf(string,"bad");
  911.                             else sprintf(string,"!serial");
  912.                         }else if(grayClut!=clut->read.identity)sprintf(string,"ok");
  913.                         else sprintf(string,"!%s",colorGrayString[grayClut]);
  914.                     }else sprintf(string,"");
  915.                     ffprintf(o,s2,string);
  916.                     if(card->depth[d].pixelSize<=8)reportRGB=!clut->read.identity;
  917.                 }
  918.                 ffprintf(o,s3,"");
  919.                 if(reportRGB)ReportRGBGains(o,quickly,isGray,card);
  920.             }
  921.     
  922.             tested=0;
  923.             for(d=0;d<6;d++)if(card->depth[d].pixelSize)
  924.                 if(card->depth[d].clut[quickly][isGray].visual.tested)tested=1;
  925.             if(tested){
  926.                 sprintf(string,"%s visual inspection",setEntriesString[quickly]);
  927.                 ffprintf(o,s1,string);
  928.                 for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  929.                     clut=&card->depth[d].clut[quickly][isGray];
  930.                     if(clut->visual.tested){
  931.                         if(clut->visual.errors){
  932.                             if(!clut->visual.errorsAtOnce)sprintf(string,"!serial");
  933.                             else sprintf(string,"bad");
  934.                         }else sprintf(string,"ok");
  935.                     }else sprintf(string,"");
  936.                     ffprintf(o,s2,string);
  937.                 }
  938.                 ffprintf(o,s3,"");
  939.             }
  940.         }
  941.     }
  942.     for(i=0;i<2;i++)if(o[i]!=NULL)fflush(o[i]);    // Save to disk, just in case.
  943. }
  944.  
  945. // Print a 3x3 color matrix representing a best fit to the observed relation between
  946. // the colors we read and the colors we write.
  947. void ReportRGBGains(FILE *o[2],Boolean quickly,Boolean isGray,VideoInfo *card)
  948. {
  949.     short j,k,d,depths;
  950.     VideoCardClutTest *clut;
  951.     double rgbGain[3][3],rgbError[3];
  952.     Boolean integralGains,bad;
  953.  
  954.     depths=0;
  955.     for(j=0;j<3;j++){
  956.         for(k=0;k<3;k++)rgbGain[j][k]=0.;
  957.         rgbError[j]=0.0;
  958.     }
  959.     integralGains=1;
  960.     for(d=0;d<6;d++)if(card->depth[d].pixelSize && card->depth[d].pixelSize<=8){
  961.         clut=&card->depth[d].clut[quickly][isGray];
  962.         bad=0;
  963.         for(j=0;j<3;j++)bad|=clut->read.rgbError[j]>2.0*(1<<(16-4));
  964.         if(bad)continue;
  965.         for(j=0;j<3;j++){
  966.             for(k=0;k<3;k++)rgbGain[j][k]+=clut->read.rgbGain[j][k];
  967.             rgbError[j]+=clut->read.rgbError[j];
  968.         }
  969.         depths++;
  970.     }
  971.     for(j=0;j<3;j++){
  972.         for(k=0;k<3;k++){
  973.             rgbGain[j][k]/=depths;
  974.             integralGains&=(rgbGain[j][k]==floor(rgbGain[j][k]));
  975.         }
  976.         rgbError[j]/=depths;
  977.     }
  978.     if(IsFinite(rgbGain[0][0]))for(j=0;j<3;j++){
  979.         ffprintf(o," (%cOut±%4.1f%%)%c"
  980.             ,"RGB"[j],rgbError[j]*100./USHRT_MAX," = "[j]);
  981.         if(integralGains)ffprintf(o,"(%1.0f %1.0f %1.0f)"
  982.             ,rgbGain[j][0],rgbGain[j][1],rgbGain[j][2]);
  983.         else ffprintf(o,"(%3.2f %3.2f %3.2f)"
  984.             ,rgbGain[j][0],rgbGain[j][1],rgbGain[j][2]);
  985.         ffprintf(o,"%c(%cIn)\n"," x "[j],"RGB"[j]);
  986.     }
  987. }
  988.  
  989. // On Mac IIci, Sys 6.07, HasDepth returns "mode" of 0x100 at all legal depths.
  990. // and SetDepth only accepts mode, not depth.
  991. //            ok=HasDepth(card[i].device,oldPixelSize,1,oldIsColor);
  992. //            if(ok)error=SetDepth(card[i].device,oldDepthMode,1,oldIsColor);
  993.  
  994. void SaveAndRestoreDevice(GDHandle device)
  995. {
  996.     static Boolean firstTime=1,displayMgrOk,colorQdOk;
  997.     static GDHandle oldDevice=NULL;
  998.     static unsigned short oldDepthMode=firstVidMode,oldIsColor=1;
  999.     static unsigned long oldDisplayModeID=kDisplayModeIDInvalid,displayModeID;
  1000.     unsigned long depthModeUL;
  1001.     int error;
  1002.     long value;
  1003.  
  1004.     if(firstTime){
  1005.         Gestalt(gestaltSystemVersion,&value);
  1006.         assert(value>=0x605);    // need New Palette Manager for SetDepth
  1007.         Gestalt(gestaltQuickdrawVersion,&value);
  1008.         colorQdOk=value>=gestalt8BitQD;
  1009.         value=0;
  1010.         Gestalt(gestaltDisplayMgrAttr,&value);
  1011.         displayMgrOk=value&(1<<gestaltDisplayMgrPresent);
  1012.         firstTime=0;
  1013.     }
  1014.     if(colorQdOk && device!=oldDevice){
  1015.         // restore old device to old state
  1016.         if(oldDevice!=NULL){
  1017.             if(displayMgrOk && oldDisplayModeID!=kDisplayModeIDInvalid){
  1018.                 error=GDGetDisplayMode(oldDevice,&displayModeID,NULL,NULL,NULL);
  1019.                 if(displayModeID!=oldDisplayModeID){
  1020.                     depthModeUL=oldDepthMode;
  1021.                     error=DMSetDisplayMode(oldDevice,oldDisplayModeID,&depthModeUL,0,NULL);
  1022.                 }
  1023.             }
  1024.             error=SetDepth(oldDevice,oldDepthMode,1,oldIsColor);
  1025.         }
  1026.         // save old state of new device
  1027.         oldDevice=device;
  1028.         oldDisplayModeID=kDisplayModeIDInvalid;    // default, in case call fails
  1029.         if(oldDevice!=NULL){
  1030.             if(displayMgrOk)
  1031.                 error=GDGetDisplayMode(oldDevice,&oldDisplayModeID,&oldDepthMode,NULL,NULL);
  1032.             oldDepthMode=(**device).gdMode;
  1033.             oldIsColor=TestDeviceAttribute(device,gdDevType);
  1034.         }
  1035.     }
  1036. }
  1037.  
  1038.